package cool.taomu.toolkit

import com.google.inject.Injector
import com.google.inject.Module
import cool.taomu.toolkit.platform.inter.IPlatform
import cool.taomu.toolkit.platform.inter.PriorityOrder
import java.io.BufferedReader
import java.io.IOException
import java.io.InputStreamReader
import java.net.URL
import java.security.AccessControlContext
import java.security.AccessController
import java.security.PrivilegedAction
import java.util.ArrayList
import java.util.Enumeration
import java.util.Iterator
import java.util.LinkedHashMap
import java.util.List
import java.util.Map
import java.util.NoSuchElementException
import java.util.Objects
import java.util.ServiceConfigurationError
import org.slf4j.LoggerFactory

final class TaomuGuiceServiceLoader<S> implements Iterable<S> {

	static val String PREFIX = "META-INF/services/";

	val Class<S> service;

	val ClassLoader loader;

	val AccessControlContext acc;

	LinkedHashMap<String, S> providers = new LinkedHashMap<String, S>();

	LazyIterator<S> lookupIterator;

	Injector injector;

	def reload() {
		providers.clear();
		lookupIterator = new LazyIterator(service, loader, acc, providers, injector);
	}

	private new(Class<S> svc, Injector injector, ClassLoader cl) {
		service = Objects.requireNonNull(svc, "Service interface cannot be null");
		loader = (cl === null) ? ClassLoader.getSystemClassLoader() : cl;
		acc = (System.getSecurityManager() !== null) ? AccessController.getContext() : null;
		this.injector = injector;
		reload();
	}

	private static def fail(Class<?> service, String msg, Throwable cause) throws ServiceConfigurationError
    {
		throw new ServiceConfigurationError(service.getName() + ": " + msg, cause);
	}

	private static def fail(Class<?> service, String msg) throws ServiceConfigurationError
    {
		throw new ServiceConfigurationError(service.getName() + ": " + msg);
	}

	private static def fail(Class<?> service, URL u, int line, String msg) throws ServiceConfigurationError
    {
		fail(service, u + ":" + line + ": " + msg);
	}

	override iterator() {
		return new Iterator<S>() {
			var Iterator<Map.Entry<String, S>> knownProviders = providers.entrySet().iterator();

			override boolean hasNext() {
				if (knownProviders.hasNext()) {
					return true;
				}
				return lookupIterator.hasNext();
			}

			override S next() {
				if (knownProviders.hasNext())
					return knownProviders.next().getValue();
				return lookupIterator.next();
			}

			override void remove() {
				throw new UnsupportedOperationException();
			}

		};
	}

	private static class LazyIterator<S> implements Iterator<S> {
		Class<S> service;
		ClassLoader loader;
		Enumeration<URL> configs = null;
		Iterator<String> pending = null;
		AccessControlContext acc;
		LinkedHashMap<String, S> providers
		String nextName = null;
		Injector injector;

		new(Class<S> service, ClassLoader loader, AccessControlContext acc, LinkedHashMap<String, S> providers,
			Injector injector) {
			this.service = service;
			this.loader = loader;
			this.acc = acc;
			this.providers = providers;
			this.injector = injector
		}

		private def int parseLine(Class<?> service, URL u, BufferedReader r, int lc, List<String> names) throws IOException, ServiceConfigurationError
    {
			var String ln = r.readLine();
			if (ln === null) {
				return -1;
			}
			var int ci = ln.indexOf('#');
			if (ci >= 0) {
				ln = ln.substring(0, ci);
			}
			ln = ln.trim();
			var int n = ln.length();
			if (n != 0) {
				if ((ln.indexOf(' ') >= 0) || (ln.indexOf('\t') >= 0)) {
					fail(service, u, lc, "Illegal configuration-file syntax");
				}
				var int cp = ln.codePointAt(0);
				if (!Character.isJavaIdentifierStart(cp)) {
					fail(service, u, lc, "Illegal provider-class name: " + ln);
				}
				for (var int i = Character.charCount(cp); i < n; i += Character.charCount(cp)) {
					cp = ln.codePointAt(i);
					if (!Character.isJavaIdentifierPart(cp) &&
						(cp != '.' && !new String(Character.toChars(cp)).equals("."))) {
						fail(service, u, lc, "Illegal provider-class name: " + ln);
					}
				}
				if (!providers.containsKey(ln) && !names.contains(ln)) {
					names.add(ln);
				}
			}
			return lc + 1;
		}

		private def Iterator<String> parse(Class<?> service, URL u) throws ServiceConfigurationError
    {
			var ArrayList<String> names = new ArrayList<String>();
			try (var in = u.openStream()) {
				var in1 = in;
				try(var r = new BufferedReader(new InputStreamReader(in1, "utf-8"))) {
					var int lc = 1;
					while ((lc = parseLine(service, u, r, lc, names)) >= 0) {
					}
				}
			} catch (IOException x) {
				fail(service, "Error reading configuration file", x);
			}
			return names.iterator();
		}

		def hasNextService() {
			if (nextName !== null) {
				return true;
			}
			if (configs === null) {
				try {
					var String fullName = PREFIX + service.getName();
					if (loader === null) {
						configs = ClassLoader.getSystemResources(fullName);
					} else {
						configs = loader.getResources(fullName);
					}
				} catch (IOException x) {
					fail(service, "Error locating configuration files", x);
				}
			}
			while ((pending === null) || !pending.hasNext()) {
				if (!configs.hasMoreElements()) {
					return false;
				}
				pending = parse(service, configs.nextElement());
			}
			nextName = pending.next();
			return true;
		}

		private def S nextService() {
			if (!hasNextService())
				throw new NoSuchElementException();
			var String cn = nextName;
			nextName = null;
			var Class<?> c = null;
			try {
				c = Class.forName(cn, false, loader);
			} catch (ClassNotFoundException x) {
				fail(service, "Provider " + cn + " not found");
			}
			if (!service.isAssignableFrom(c)) {
				fail(service, "Provider " + cn + " not a subtype");
			}
			try {
				var p = service.cast(this.injector.getInstance(c))
				providers.put(cn, p);
				return p;
			} catch (Throwable x) {
				fail(service, "Provider " + cn + " could not be instantiated", x);
			}
			throw new Error(); // This cannot happen
		}

		override hasNext() {
			if (acc === null) {
				return hasNextService();
			} else {
				var PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
					override Boolean run() { return hasNextService(); }
				};
				return AccessController.doPrivileged(action, acc);
			}
		}

		override next() {
			if (acc === null) {
				return nextService();
			} else {
				var PrivilegedAction<S> action = new PrivilegedAction<S>() {
					override S run() { return nextService(); }
				};
				return AccessController.doPrivileged(action, acc);
			}
		}

		override void remove() {
			throw new UnsupportedOperationException();
		}
	}

	def static <S> TaomuGuiceServiceLoader<S> load(Class<S> service, Injector injector, ClassLoader loader) {
		new TaomuGuiceServiceLoader<S>(service, injector, loader);
	}

	def static <S> TaomuGuiceServiceLoader<S> load(Class<S> service, Injector injector) {
		var ClassLoader cl = Thread.currentThread().getContextClassLoader();
		return TaomuGuiceServiceLoader.load(service, injector, cl);
	}

	def static <S> TaomuGuiceServiceLoader<S> loadInstalled(
		Class<S> service,
		Injector injector
	) {
		var ClassLoader cl = ClassLoader.getSystemClassLoader();
		var ClassLoader prev = null;
		while (cl !== null) {
			prev = cl;
			cl = cl.getParent();
		}
		return TaomuGuiceServiceLoader.load(service, injector, prev);
	}

	override String toString() {
		return "java.util.ServiceLoader[" + service.getName() + "]";
	}

	val static LOG = LoggerFactory.getLogger(TaomuGuiceServiceLoader);

	def static TaomuGuiceServiceLoader<IPlatform> loadIPlatform(Injector injector, TaomuConfigure crypto) {
		var ClassLoader cl = Thread.currentThread().getContextClassLoader();
		return loadIPlatform(injector, crypto, cl)
	}

	def static TaomuGuiceServiceLoader<IPlatform> loadIPlatform(Injector injector, TaomuConfigure config,
		ClassLoader loader) {
		var sloads = new TaomuGuiceServiceLoader<IPlatform>(IPlatform, injector, loader);
		LOG.info("发现插件:{}个", sloads.size);
		if (sloads.size > 0) {
			val pks = new ArrayList<String>();
			val modules = new ArrayList<Module>();
			var sloadList = sloads.toList
			sloadList.sort([ s1, s2 |
				var boolean p1 = s1.getClass().isAnnotationPresent(PriorityOrder);
				var boolean p2 = s2.getClass().isAnnotationPresent(PriorityOrder);

				if(p1 && !p2) return -1; // s1 优先
				if(!p1 && p2) return 1; // s2 优先
				return s1.getClass().getName().compareTo(s2.getClass().getName());
			])
			sloadList.forEach [
				LOG.info("插件:{}", it.note);
				if (it.packages !== null) {
					var pk = it.packages()
					LOG.info("package:{}", pk);
					pks.add(pk);
				}
				var a = it.module(config);
				if (a !== null) {
					modules.add(a)
				}
			]
			modules.add(TaomuGuice.bindConstant(#{TaomuGuice.JESERY_SERVICELOADER_PACKAGES -> pks.join(",")}))
			val cinjector = injector.createChildInjector(modules);
			injector.getInstance(Context).setSupplierInjector(cinjector);
			sloads.forEach [
				it.init();
			]
			sloads.forEach [
				it.start();
			]
		}
		return sloads;
	}
}
